#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <algorithm>

#include "../SysMonCommon/SysMonCommon.h"

int 
Error(const std::string& ErrorString);

VOID 
DisplayTime(const LARGE_INTEGER& Time);

VOID 
DisplayInfo(BYTE* bBuffer, DWORD dwSize);

VOID
DisplayBinary(const UCHAR* buffer, DWORD dwSize);

int 
main(VOID)
{
	HANDLE hFile = ::CreateFile(L"\\\\.\\SysMon", GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
	if (hFile == INVALID_HANDLE_VALUE) {
		return Error("Failed to open file");
	}

	BYTE* bBuffer = new BYTE[1 << 16];

	while (TRUE) {
		DWORD dwBytes = 0;
		if (::ReadFile(hFile, bBuffer, 1 << 16, &dwBytes, nullptr) == FALSE) {
			return Error("Failed to read");
		}
		if (dwBytes != 0) {
			DisplayInfo(bBuffer, dwBytes);
		}
		::Sleep(200);
	}
	delete[] bBuffer;
	CloseHandle(hFile);
	return 0;
}

int 
Error(const std::string& ErrorString)
{
	std::cout << "Error: " << ErrorString << ": " << GetLastError() << std::endl;
	::system("pause");
	return 1;
}

VOID 
DisplayTime(const LARGE_INTEGER& Time)
{
	SYSTEMTIME St;
	::FileTimeToSystemTime((FILETIME*)&Time, &St);
	printf("%02d.%02d.%02d.%03d: ", St.wHour, St.wMinute, St.wSecond, St.wMilliseconds);
}

VOID 
DisplayInfo(BYTE* bBuffer, DWORD dwSize)
{
	DWORD dwCount = dwSize; 
	while (dwCount > 0) {
		ItemHeader* Header = (ItemHeader*)bBuffer;

		switch (Header->m_Type) {
		case ItemType::ProcessExit: {
			DisplayTime(Header->m_Time);
			ProcessExitInfo* Info = (ProcessExitInfo*)bBuffer;
			printf("Process %d Exited\n", Info->m_ProcessId);
			break;
		}
		case ItemType::ProcessCreate: {
			DisplayTime(Header->m_Time);
			ProcessCreateInfo* Info = (ProcessCreateInfo*)bBuffer;
			std::wstring CommandLine((WCHAR*)(bBuffer + Info->m_CommandLineOffset), Info->m_CommandLineLength);
			wprintf(L"Process %lu Created. Command line: %ls\n", Info->m_ProcessId, CommandLine.c_str());
			break;
		}
		case ItemType::ThreadCreate: {
			DisplayTime(Header->m_Time);
			ThreadCreateExitInfo* Info = (ThreadCreateExitInfo*)bBuffer;
			printf("Thread %d Created in process %d\n", Info->m_ThreadId, Info->m_ProcessId);
			break;
		}
		case ItemType::ThreadExit: {
			DisplayTime(Header->m_Time);
			ThreadCreateExitInfo* Info = (ThreadCreateExitInfo*)bBuffer;
			printf("Thread %d Exited form process %d\n", Info->m_ThreadId, Info->m_ProcessId);
			break;
		}
		case ItemType::ImageLoad: {
			DisplayTime(Header->m_Time);
			ImageLoadInfo* Info = (ImageLoadInfo*)bBuffer;
			printf("Image loaded into process %d at address 0x%p (%ws)\n", Info->m_ProcessId, Info->m_LoadAddress, Info->m_ImageFileName);
			break;
		}
		case ItemType::RegistrySetValue: {
			DisplayTime(Header->m_Time);
			RegistrySetValueInfo* Info = (RegistrySetValueInfo*)bBuffer;
			printf("Registry write PID = %d: %ws\\%ws type: %d size: %d data: ", Info->m_ProcessId, Info->m_KeyName, Info->m_ValueName, Info->m_DataType, Info->m_DataSize);

			switch (Info->m_DataType) {
			case REG_DWORD: {
				printf("0x%08x\n", *(DWORD*)Info->m_Data);
				break;
			}
			case REG_SZ:
			case REG_EXPAND_SZ: {
				printf("%ws\n", (WCHAR*)Info->m_Data);
				break;
			}
			case REG_BINARY: {
				DisplayBinary(Info->m_Data, min(Info->m_DataSize, sizeof(Info->m_Data)));
				break;
			}
			default: {
				DisplayBinary(Info->m_Data, min(Info->m_DataSize, sizeof(Info->m_Data)));
			}
			}
			break;
		}
		}
		bBuffer += Header->m_Size;
		dwCount -= Header->m_Size;
	}
}

VOID
DisplayBinary(const UCHAR* buffer, DWORD dwSize)
{
	for (DWORD i = 0; i < dwSize; i++) {
		printf("%02X ", buffer[i]);
	}
	printf("\n");
}